// Copyright 2023 Innkeeper Lukexp(王晓平) &lt;37196496@qq.com>. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. The original repo for
// this file is https://gitee.com/jetxt/jinyu.

package uploader

import (
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type Uploader struct {
	Dir        string
	AllowTypes []string
	DenyTypes  []string
	MaxSize    int64
}

func NewUploader(opts ...Option) *Uploader {
	uploader := &Uploader{}
	for _, o := range opts {
		o(uploader)
	}
	return uploader
}

type Option func(*Uploader)

func WithDir(dir string) Option {
	return func(u *Uploader) {
		u.Dir = dir
	}
}

func WithAllowTypes(types ...string) Option {
	return func(u *Uploader) {
		u.AllowTypes = make([]string, 0, len(types))
		u.AllowTypes = append(u.AllowTypes, types...)
	}
}

func WithDenyTypes(types ...string) Option {
	return func(u *Uploader) {
		u.DenyTypes = make([]string, 0, len(types))
		u.DenyTypes = append(u.DenyTypes, types...)
	}
}

func WithMaxSize(max int64) Option {
	return func(u *Uploader) {
		u.MaxSize = max
	}
}

type UFile struct {
	Filepath string //文件存储路径包括文件名
	Filename string //存储文件名
	Name     string //文件名
	Path     string //文件路径（相对路径）
	Size     int64  //文件大小
	Type     string //文件类型
}

func newUpFile(u *Uploader, dir ...string) (*UFile, error) {
	uf := &UFile{}
	rootPath, folderName, err := u.getDir(dir...)
	if err != nil {
		return uf, err
	}
	uf.Filepath = rootPath
	uf.Path = folderName
	return uf, nil
}

// Form上传
func (u *Uploader) UploadFormFile(f *multipart.FileHeader, dir ...string) (*UFile, error) {
	uf, err := newUpFile(u)
	if err != nil {
		return uf, err
	}
	// 生成文件名,组装UpFile属性
	uf.Size = f.Size
	uf.Type = path.Ext(f.Filename)
	uf.Name = f.Filename
	uf.GenerateName()

	// 验证文件，大小，类型
	if err := uf.Validate(u); err != nil {
		return uf, err
	}

	// 保存文件
	if err := copyFile(f, uf.Filepath); err != nil {
		return uf, err
	}

	return uf, nil
}

// form 上传图片
func (u *Uploader) UploadImage(f *multipart.FileHeader, dir ...string) (*UFile, error) {
	return u.UploadFormFile(f, dir...)
}

// base64上传图片
func (u *Uploader) UploadBase64Image(image string, dir ...string) (*UFile, error) {
	f, err := newUpFile(u, dir...)
	if err != nil {
		return f, err
	}
	size := len(image) - len(image)/8*2
	f.Size = int64(size)

	fmt.Println("Base64 image size:", f.Size)

	b, _ := regexp.MatchString(`^data:\s*image\/(\w+);base64,`, image)
	if !b {
		return f, ERR_BASE64_FORMAT
	}

	re, _ := regexp.Compile(`^data:\s*image\/(\w+);base64,`)
	allData := re.FindAllSubmatch([]byte(image), 2)
	f.Type = "." + string(allData[0][1]) //png ，jpeg 后缀获取

	f.GenerateName()

	//验证
	if err := f.Validate(u); err != nil {
		return f, err
	}

	base64Str := re.ReplaceAllString(image, "")

	bytes, _ := base64.StdEncoding.DecodeString(base64Str)

	err = ioutil.WriteFile(f.Filepath, bytes, 0666)
	if err != nil {
		return f, err
	}

	//获取图片，读取图片的大小
	fi, err := os.Stat(f.Filepath)
	if err != nil {
		return f, err
	}
	f.Size = fi.Size()
	f.Name = f.Filename

	return f, nil
}

// 包内置上传对象实例
var std *Uploader

func getStdUploader() *Uploader {
	if std == nil {
		std = NewUploader(
			WithAllowTypes("jpg", "jpeg", "png", "bmp", "pdf", "xls", "xlsx", "doc", "docx", "ppt", "pptx"),
			WithMaxSize(1024*1024*10),
		)
	}
	return std
}

func UploadFile(f *multipart.FileHeader, dir ...string) (*UFile, error) {
	return getStdUploader().UploadFormFile(f, dir...)
}

func UploadImage(f *multipart.FileHeader, dir ...string) (*UFile, error) {
	return getStdUploader().UploadImage(f, dir...)
}

func UploadBase64Image(image string, dir ...string) (*UFile, error) {
	return getStdUploader().UploadBase64Image(image, dir...)
}

func (u *Uploader) getDir(dir ...string) (string, string, error) {
	var rootDir string = ""
	if u.Dir == "" {
		rootDir = getCurrentPath()
		rootDir = rootDir + "upload/"
	} else {
		rootDir = u.Dir
	}

	folderName := time.Now().Format("2006/01/02")
	if len(dir) > 0 {
		folderName = filepath.Join(dir[0], "/"+folderName)
	}

	folderPath := filepath.Join(rootDir, folderName)
	if err := os.MkdirAll(folderPath, os.ModePerm); err != nil {
		return folderPath, folderName, err
	}

	return folderPath, folderName, nil
}

func (f *UFile) GenerateName() {
	fileNameInt := time.Now().Unix()
	f.Filename = strconv.FormatInt(fileNameInt, 10) + f.Type
	f.Filepath = f.Filepath + "/" + f.Filename
}

func (f *UFile) Validate(uf *Uploader) error {
	ext := strings.TrimLeftFunc(f.Type, func(r rune) bool { return r == '.' })
	ext = strings.ToLower(ext)
	// 1. 验证文件类型,如果有deny类型，则判断deny类型，如果没有deny，则判断allow类型
	if len(uf.DenyTypes) > 0 {
		if in_array(ext, uf.DenyTypes) {
			return ERR_NOTALLOWED_TYPE
		}
	} else if len(uf.AllowTypes) > 0 {
		if ok := in_array(ext, uf.AllowTypes); !ok {
			return ERR_NOTALLOWED_TYPE
		}
	}

	// 2. 验证文件大小
	if uf.MaxSize > 0 {
		if f.Size > uf.MaxSize {
			return ERR_SIEZE_TOOLARGE
		}
	}

	return nil
}

func getCurrentPath() string {
	file, err := exec.LookPath(os.Args[0])
	if err != nil {
		return "./"
	}
	path, err := filepath.Abs(file)
	if err != nil {
		return "./"
	}
	i := strings.LastIndex(path, "/")
	if i < 0 {
		i = strings.LastIndex(path, "\\")
	}
	if i < 0 {
		return "./"
	}
	return string(path[0 : i+1])
}

func copyFile(file *multipart.FileHeader, dst string) error {
	src, err := file.Open()
	if err != nil {
		return err
	}
	defer src.Close()

	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer out.Close()

	_, err = io.Copy(out, src)
	return err
}

func in_array(str string, arr []string) bool {
	for _, v := range arr {
		if v == str {
			return true
		}
	}
	return false
}
